package com.kjlink.widget.stat.mybatisXml;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.IOException;
import java.io.StringReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Stream;

public class MyBatisXmlStatistic  {
    // 需要统计的SQL标签
    private static final Set<String> SQL_TAGS = new HashSet<>(Arrays.asList(
            "select", "insert", "update", "delete", "sql"
    ));

    // 统计结果数据结构
    public static class StatisticResult {
        private String rootFolderPath;
        private int xmlCount;
        private Map<String, Integer> tagCounts = new HashMap<>();

        public StatisticResult(String rootFolderPath) {
            this.rootFolderPath = rootFolderPath;
        }

        public void incrementXmlCount() {
            xmlCount++;
        }

        public void incrementTagCount(String tagName) {
            tagCounts.put(tagName, tagCounts.getOrDefault(tagName, 0) + 1);
        }

        public int getTotalTags() {
            return tagCounts.values().stream().mapToInt(Integer::intValue).sum();
        }

        // getter方法
        public String getRootFolderPath() {
            return rootFolderPath;
        }

        public int getXmlCount() {
            return xmlCount;
        }

        public Map<String, Integer> getTagCounts() {
            return new HashMap<>(tagCounts);
        }
    }

    /**
     * 扫描指定根目录及其所有子目录，将结果汇总到根目录
     * @param rootDirectories 要扫描的根目录列表
     * @return 统计结果列表
     */
    public static List<StatisticResult> scanAndStatistic(List<String> rootDirectories) {
        List<StatisticResult> results = new ArrayList<>();

        // 为每个根目录创建统计结果对象
        Map<String, StatisticResult> rootResultMap = new HashMap<>();
        for (String rootDir : rootDirectories) {
            Path rootPath = Paths.get(rootDir);
            if (Files.exists(rootPath) && Files.isDirectory(rootPath)) {
                rootResultMap.put(rootDir, new StatisticResult(rootDir));
                results.add(rootResultMap.get(rootDir));
            } else {
                System.err.println("目录不存在或不是文件夹: " + rootDir);
            }
        }

        // 遍历每个根目录，递归扫描所有子目录
        for (String rootDir : rootDirectories) {
            Path rootPath = Paths.get(rootDir);

            if (!Files.exists(rootPath) || !Files.isDirectory(rootPath)) {
                continue;
            }

            StatisticResult result = rootResultMap.get(rootDir);
            if (result == null) {
                continue;
            }

            // 递归扫描所有子目录下的XML文件
            try (Stream<Path> pathStream = Files.walk(rootPath)) {
                pathStream.filter(Files::isRegularFile)
                        .filter(path -> path.getFileName().toString().toLowerCase().endsWith(".xml"))
                        .forEach(path -> processXmlFile(path, result));
            } catch (Exception e) {
                System.err.println("扫描目录 " + rootDir + " 时发生错误: " + e.getMessage());
            }
        }

        return results;
    }

    /**
     * 处理单个XML文件，统计其中的标签（已修复内网解析问题）
     */
    private static void processXmlFile(Path xmlPath, StatisticResult result) {
        try {
            // 增加XML文件计数
            result.incrementXmlCount();

            // 配置XML解析器，禁用外部网络请求（兼容版本）
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

            try {
                // 1. 禁用外部DTD加载
                factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
            } catch (Exception e) {
                // 某些解析器可能不支持此特性，忽略异常
            }

            try {
                // 2. 禁用外部实体
                factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
                factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
            } catch (Exception e) {
                // 某些解析器可能不支持此特性，忽略异常
            }

            // 移除不兼容的SecureProcessing特性

            DocumentBuilder builder = factory.newDocumentBuilder();

            // 3. 设置实体解析器，忽略所有外部请求
            builder.setEntityResolver(new EntityResolver() {
                @Override
                public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
                    // 返回空输入源，避免网络请求
                    return new InputSource(new StringReader(""));
                }
            });

            Document document = builder.parse(xmlPath.toFile());
            document.getDocumentElement().normalize();

            // 递归统计所有标签
            countTags(document.getDocumentElement(), result);

        } catch (Exception e) {
            System.err.println("处理文件 " + xmlPath + " 时发生错误: " + e.getMessage());
        }
    }

    /**
     * 递归统计XML元素中的标签
     */
    private static void countTags(Element element, StatisticResult result) {
        String tagName = element.getTagName();
        if (SQL_TAGS.contains(tagName)) {
            result.incrementTagCount(tagName);
        }

        // 处理子元素
        NodeList nodeList = element.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                countTags((Element) node, result);
            }
        }
    }

    /**
     * 打印统计结果
     */
    public static void printResults(List<StatisticResult> results) {
        System.out.println("=== MyBatis XML文件统计结果 ===");
        System.out.println();

        // 打印每个根目录的统计结果（包含所有子目录）
        for (StatisticResult result : results) {
            System.out.println("根目录: " + result.getRootFolderPath());
            System.out.println("  包含所有子目录的XML文件总数: " + result.getXmlCount());
            System.out.println("  标签统计:");

            result.getTagCounts().entrySet().stream()
                    .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
                    .forEach(entry -> System.out.printf("    %-6s: %d%n", entry.getKey(), entry.getValue()));

            System.out.println("  标签总数: " + result.getTotalTags());
            System.out.println();
        }

        // 计算并打印汇总信息
        calculateAndPrintSummary(results);
    }

    /**
     * 计算并打印汇总信息
     */
    private static void calculateAndPrintSummary(List<StatisticResult> results) {
        int totalXmlFiles = 0;
        Map<String, Integer> totalTagCounts = new HashMap<>();

        for (StatisticResult result : results) {
            totalXmlFiles += result.getXmlCount();
            result.getTagCounts().forEach((tag, count) ->
                    totalTagCounts.put(tag, totalTagCounts.getOrDefault(tag, 0) + count)
            );
        }

        int totalTags = totalTagCounts.values().stream().mapToInt(Integer::intValue).sum();

        System.out.println("=== 汇总统计 ===");
        System.out.println("总根目录数量: " + results.size());
        System.out.println("所有目录（含子目录）的总XML文件数量: " + totalXmlFiles);
        System.out.println("总标签数量:");

        totalTagCounts.entrySet().stream()
                .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
                .forEach(entry -> System.out.printf("  %-6s: %d%n", entry.getKey(), entry.getValue()));

        System.out.println("标签总数: " + totalTags);
    }

    // 测试方法
    public static void main(String[] args) {
        // 要扫描的根目录列表
        List<String> rootDirectories = Arrays.asList(
                "E:\\space\\idea\\basic-support\\lcxm-project-blog\\lcxm-project-blog-common\\src\\main\\java",
                "E:\\space\\idea\\basic-support\\lcxm-project-blog\\lcxm-project-blog-common\\src\\main\\resources",
                "E:\\space\\idea\\work-kjlink\\work-report\\src\\main\\java"
        );

        // 执行扫描和统计
        List<StatisticResult> results = scanAndStatistic(rootDirectories);

        // 打印结果
        printResults(results);
    }
}
